package org.dromara.patrol.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.google.common.collect.Lists;
import io.seata.spring.annotation.GlobalTransactional;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.patrol.PatrolSnapException;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.patrol.domain.PatrolSnapPlan;
import org.dromara.patrol.domain.bo.PatrolSnapPlanBo;
import org.dromara.patrol.domain.bo.PatrolSnapQuery;
import org.dromara.patrol.domain.model.snap.DeviceChannel;
import org.dromara.patrol.domain.model.snap.SnapExecuteRule;
import org.dromara.patrol.domain.model.snap.SnapTime;
import org.dromara.patrol.domain.vo.PatrolSnapPlanVo;
import org.dromara.patrol.mapper.PatrolSnapPlanMapper;
import org.dromara.patrol.service.IPatrolSnapPlanService;
import org.dromara.system.api.RemoteStoreService;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import tech.powerjob.client.PowerJobClient;
import tech.powerjob.common.model.LifeCycle;
import tech.powerjob.common.request.http.SaveJobInfoRequest;
import tech.powerjob.common.response.ResultDTO;


import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

import static org.dromara.common.core.enums.patrol.snap.PlanStatus.ENABLED;
import static org.dromara.common.core.enums.patrol.snap.RepeatType.CURRENT_DAY;
import static tech.powerjob.common.enums.ExecuteType.STANDALONE;
import static tech.powerjob.common.enums.ProcessorType.BUILT_IN;
import static tech.powerjob.common.enums.TimeExpressionType.DAILY_TIME_INTERVAL;

/**
 * 巡店管理-巡店抓拍配置Service业务层处理
 *
 * @author LionLi
 * @date 2024-03-27
 */
@RequiredArgsConstructor
@Service
public class PatrolSnapPlanServiceImpl implements IPatrolSnapPlanService {

    private final PatrolSnapPlanMapper baseMapper;

    private final PowerJobClient powerJobClient;

    @DubboReference
    private RemoteStoreService remoteStoreService;

    /**
     * 查询巡店管理-巡店抓拍配置
     */
    @Override
    public PatrolSnapPlanVo queryById(Long planId){
        return baseMapper.selectVoById(planId);
    }

    /**
     * 查询巡店管理-巡店抓拍配置列表
     */
    @Override
    public TableDataInfo<PatrolSnapPlanVo> queryPageList(PatrolSnapQuery query) {
        LambdaQueryWrapper<PatrolSnapPlan> lqw = buildQueryWrapper(query);
        Page<PatrolSnapPlanVo> result = baseMapper.selectVoPage(query.build(), lqw);
        if (CollUtil.isEmpty(result.getRecords())) {
            return TableDataInfo.build(result);
        }
        List<Long> storeIds = result.getRecords().stream().map(PatrolSnapPlanVo::getStoreId).distinct().toList();
        Map<Long, String> storeNameMap = remoteStoreService.selectNamesByStoreList(storeIds);
        result.getRecords().forEach(item -> {
            if (ObjUtil.isNotNull(item)
                && ObjUtil.isNotNull(item.getStoreId())) {
                item.getNameMap().put(item.getStoreId(), storeNameMap.computeIfAbsent(item.getStoreId(), key -> null));
            }
        });
        return TableDataInfo.build(result);
    }

    /**
     * 查询巡店管理-巡店抓拍配置列表
     */
    @Override
    public List<PatrolSnapPlanVo> queryList(PatrolSnapPlanBo bo) {
        LambdaQueryWrapper<PatrolSnapPlan> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<PatrolSnapPlan> buildQueryWrapper(PatrolSnapPlanBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<PatrolSnapPlan> lqw = Wrappers.lambdaQuery();
        lqw.like(StringUtils.isNotBlank(bo.getPlanName()), PatrolSnapPlan::getPlanName, bo.getPlanName());
        lqw.eq(bo.getStoreId() != null, PatrolSnapPlan::getStoreId, bo.getStoreId());

//        lqw.eq(StringUtils.isNotBlank(bo.getDeviceIds()), PatrolSnapPlan::getDeviceIds, bo.getDeviceIds());
//        //lqw.eq(StringUtils.isNotBlank(bo.getDeviceChannel()), PatrolSnapPlan::getDeviceChannel, bo.getDeviceChannel());
//        lqw.eq(StringUtils.isNotBlank(bo.getDevicePresetPoint()), PatrolSnapPlan::getDevicePresetPoint, bo.getDevicePresetPoint());
//        lqw.eq(StringUtils.isNotBlank(bo.getLatestImgUrl()), PatrolSnapPlan::getLatestImgUrl, bo.getLatestImgUrl());
//        lqw.eq(bo.getSnapTotal() != null, PatrolSnapPlan::getSnapTotal, bo.getSnapTotal());
//        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), PatrolSnapPlan::getStatus, bo.getStatus());
//        //lqw.eq(StringUtils.isNotBlank(bo.getExecuteRule()), PatrolSnapPlan::getExecuteRule, bo.getExecuteRule());
//        lqw.eq(bo.getInspectedNum() != null, PatrolSnapPlan::getInspectedNum, bo.getInspectedNum());
//        lqw.eq(StringUtils.isNotBlank(bo.getSnapModel()), PatrolSnapPlan::getSnapModel, bo.getSnapModel());
//        lqw.eq(StringUtils.isNotBlank(bo.getExpectSnapTotal()), PatrolSnapPlan::getExpectSnapTotal, bo.getExpectSnapTotal());
        return lqw;
    }

    private LambdaQueryWrapper<PatrolSnapPlan> buildQueryWrapper(PatrolSnapQuery query) {
        LambdaQueryWrapper<PatrolSnapPlan> lqw = Wrappers.lambdaQuery();
        lqw.in(CollUtil.isNotEmpty(query.getStoreIds()), PatrolSnapPlan::getStoreId, query.getStoreIds());
        return lqw;
    }

    /**
     * 新增巡店管理-巡店抓拍配置
     */
    @Override
    @GlobalTransactional
    public Boolean insertByBo(PatrolSnapPlanBo bo) {
        PatrolSnapPlan add = MapstructUtils.convert(bo, PatrolSnapPlan.class);
        validEntityBeforeSave(add);
        add.setStatus(ENABLED.getCode());
        add.setExpectSnapTotal(getSnapTotal(bo));
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setPlanId(add.getPlanId());
            List<Long> cycleTaskIds = createCycleTask(bo);
            if (CollUtil.isNotEmpty(cycleTaskIds)) {
                add.setCycleTaskIds(cycleTaskIds);
                if (baseMapper.updateById(add) > 0) {
                    return true;
                }
                throw new PatrolSnapException("抓拍巡店任务->更新数据失败");
            } else {
                return true;
            }
        }
        throw new ServiceException("抓拍巡店任务创建失败", HttpStatus.WARN);
    }

    /**
     * 创建定时任务
     * @param bo
     * @return
     */
    private List<Long> createCycleTask(PatrolSnapPlanBo bo) {
        List<SaveJobInfoRequest> requests = getRequests(bo);
        if (CollUtil.isEmpty(requests)) {
            return Collections.emptyList();
        }
        List<Long> cycleTaskIds = Lists.newArrayListWithCapacity(requests.size());
        requests.forEach(v -> {
            ResultDTO<Long> resultDTO = powerJobClient.saveJob(v);
            if (resultDTO.isSuccess()) {
                cycleTaskIds.add(resultDTO.getData());
            } else {
                throw new PatrolSnapException("抓拍巡店任务->创建定时任务失败");
            }
        });
        return cycleTaskIds;
    }

    private List<SaveJobInfoRequest> getRequests(PatrolSnapPlanBo bo) {
        SaveJobInfoRequest request = getSaveCycleJobInfoRequest(bo);
        List<SnapTime> temp = bo.getExecuteRule().getSnapTime();
        List<SaveJobInfoRequest> requests = Lists.newArrayListWithCapacity(temp.size());
        // {"interval":"800000","startTimeOfDay":"17:49:23",
        // "endTimeOfDay":"22:55:29","intervalUnit":"SECONDS",
        // "daysOfWeek":[1,2,3,4,5,6]}
        temp.forEach(snapTime -> {
            SaveJobInfoRequest copy = BeanUtil.copyProperties(request, SaveJobInfoRequest.class);
            if (ObjUtil.isNotNull(copy)) {
                JSONObject jsonObject = new JSONObject();
                jsonObject.set("interval", snapTime.getInterval());
                jsonObject.set("startTimeOfDay", snapTime.getStartTime() + ":00");
                String endTimeStr = snapTime.getEndTime() + ":00";
                jsonObject.set("endTimeOfDay", endTimeStr);
                jsonObject.set("intervalUnit", "SECONDS");
                jsonObject.set("daysOfWeek", bo.getExecuteRule().getWeeklyRule());
                copy.setTimeExpression(JsonUtils.toJsonString(jsonObject));
                // 当任务类型为当天且计划结束时间大于当前时间加 5秒，则设置定时任务，否则不设置
                if (copy.getLifeCycle() != null) {
                    String currentStr = DateUtils
                        .parseDateToStr(DateUtils.HH_MM_SS, new Date());
                    Long endTime = DateUtils
                        .differentSecondsByDateStr(currentStr, endTimeStr, DateUtils.HH_MM_SS);
                    if (endTime >= 5 + snapTime.getInterval()) {
                        requests.add(copy);
                    }
                } else {
                    requests.add(copy);
                }
            }
        });
        return requests;
    }

    @NotNull
    private static SaveJobInfoRequest getSaveCycleJobInfoRequest(PatrolSnapPlanBo bo) {
        SaveJobInfoRequest request = new SaveJobInfoRequest();
        request.setMinCpuCores(0);
        request.setMinDiskSpace(0);
        request.setMinMemorySpace(0);
        request.setJobName(bo.getPlanName());
        request.setJobParams(JsonUtils.toJsonString(bo));
        request.setExecuteType(STANDALONE);
        if (bo.getExecuteRule().getCycleType() == CURRENT_DAY) {
            LifeCycle lifeCycle = new LifeCycle();
            lifeCycle.setStart(DateUtils.getTodayStartTime());
            lifeCycle.setEnd(DateUtils.getTodayEndTime());
            request.setLifeCycle(lifeCycle);
        }
        request.setTimeExpressionType(DAILY_TIME_INTERVAL);
        request.setProcessorType(BUILT_IN);
        request.setConcurrency(Integer.MAX_VALUE);
        request.setProcessorInfo("org.dromara.job.processors.PatrolSnapFrameExtractionProcessor");
        return request;
    }

    /**
     * 计算一天一个设备的抓拍总数
     * 最少1张，最多200张
     * @param snapExecuteRule
     * @return
     */
    private Long getOneDeviceOneDaySnapTotal(SnapExecuteRule snapExecuteRule) {
        List<SnapTime> snapTime = snapExecuteRule.getSnapTime();
        AtomicReference<Long> snapTotal = new AtomicReference<>(0L);
        for (SnapTime item : snapTime) {
            long total = DateUtils
                    .differentSecondsByDateStr(item.getStartTime(), item.getEndTime(), DateUtils.HH_MM);
            double temp = NumberUtil.div(Convert.toDouble(total), Convert.toDouble(item.getInterval()));
            snapTotal.updateAndGet(v -> v + Convert.toLong(Math.ceil(temp)) + 1L);
        }
        return snapTotal.get();
    }

    /**
     * 获取所有设备的通道数，如果不存在通道，则返回设备数
     * @param deviceChannel
     * @return
     */
    private Long getAllDeviceTotal(List<DeviceChannel> deviceChannel) {
        AtomicReference<Long> deviceTotal = new AtomicReference<>(0L);
        for (DeviceChannel item : deviceChannel) {
            deviceTotal.updateAndGet(v ->
                    v + (CollUtil.isEmpty(item.getChannelNo())
                            ? 1
                            : item.getChannelNo().size()));
        }
        return deviceTotal.get();
    }

    /**
     * 获得抓拍图片总数
     * @return
     */
    private Long getSnapTotal(PatrolSnapPlanBo bo) {
        Long deviceTotal = getAllDeviceTotal(bo.getDeviceChannel());
        Long snapTotal = getOneDeviceOneDaySnapTotal(bo.getExecuteRule());
        return deviceTotal * snapTotal;
    }

    /**
     * 修改巡店管理-巡店抓拍配置
     */
    @Override
    @GlobalTransactional
    public Boolean updateByBo(PatrolSnapPlanBo bo) {
        PatrolSnapPlan patrolSnapPlan = baseMapper.selectOne(new LambdaQueryWrapper<PatrolSnapPlan>()
            .eq(PatrolSnapPlan::getPlanId, bo.getPlanId()));
        if (ObjUtil.isNull(patrolSnapPlan)) {
            throw new ServiceException("巡店管理-巡店抓拍配置不存在", HttpStatus.MOVED_PERM);
        }
        PatrolSnapPlan update = MapstructUtils.convert(bo, PatrolSnapPlan.class);
        validEntityBeforeSave(update);
        if (CollUtil.isNotEmpty(patrolSnapPlan.getCycleTaskIds())) {
            patrolSnapPlan.getCycleTaskIds().forEach(powerJobClient::deleteJob);
        }
        List<Long> cycleTaskIds = createCycleTask(bo);
        if (CollUtil.isNotEmpty(cycleTaskIds)) {
            update.setCycleTaskIds(cycleTaskIds);
            update.setExpectSnapTotal(getSnapTotal(bo));
            if (baseMapper.updateById(update) > 0) {
                return true;
            }
            throw new ServiceException("抓拍巡店任务更新数据失败", HttpStatus.WARN);
        } else {
            return true;
        }
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(PatrolSnapPlan entity){
        if (ObjUtil.isNull(entity)) {
            throw new PatrolSnapException("数据不能为空");
        }
    }

    /**
     * 批量删除巡店管理-巡店抓拍配置
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if(isValid){
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
    }

    /**
     * 详情巡店管理-巡店抓拍配置
     * @param id
     * @return
     */
    @Override
    public Boolean deleteById(Long id) {
        PatrolSnapPlan patrolSnapPlan = baseMapper.selectOne(new LambdaQueryWrapper<PatrolSnapPlan>()
            .eq(PatrolSnapPlan::getPlanId, id));
        if (ObjUtil.isNotNull(patrolSnapPlan)) {
            if (baseMapper.deleteById(id) > 0) {
                // 删除定时任务
                if (CollUtil.isNotEmpty(patrolSnapPlan.getCycleTaskIds())) {
                    patrolSnapPlan.getCycleTaskIds().forEach(powerJobClient::deleteJob);
                }
                return true;
            }
        }
        return false;
    }

    /**
     * 更新状态
     * @param id
     * @param status
     * @return
     */
    @Override
    @GlobalTransactional
    public Boolean updateStatus(Long id, String status) {
        PatrolSnapPlan patrolSnapPlan = baseMapper.selectOne(new LambdaQueryWrapper<PatrolSnapPlan>()
            .eq(PatrolSnapPlan::getPlanId, id));
        if (ObjUtil.isNull(patrolSnapPlan)) {
            throw new ServiceException("任务状态异常", HttpStatus.MOVED_PERM);
        }
        String oldStatus = patrolSnapPlan.getStatus();
        String newStatus = ENABLED.getCode().equals(status) ? "0" : "1";
        if (baseMapper.update(new LambdaUpdateWrapper<PatrolSnapPlan>()
            .eq(PatrolSnapPlan::getPlanId, id)
            .set(PatrolSnapPlan::getStatus, newStatus)) > 0) {
            if (!oldStatus.equals(newStatus)
                && CollUtil.isNotEmpty(patrolSnapPlan.getCycleTaskIds())) {
                // 更新定时任务状态
                if (ENABLED.getCode().equals(newStatus)) {
                    patrolSnapPlan.getCycleTaskIds().forEach(powerJobClient::enableJob);
                } else {
                    patrolSnapPlan.getCycleTaskIds().forEach(powerJobClient::disableJob);
                }
            }
            return true;
        }
        throw new ServiceException("更新失败", HttpStatus.WARN);
    }


    /**
     * 获取单日预计抓拍图片数
     * @param storeIds
     * @return
     */
    @Override
    public Long queryTotal(List<Long> storeIds) {
        PatrolSnapPlan patrolSnapPlan = baseMapper.selectOne(new QueryWrapper<PatrolSnapPlan>()
                .in(CollUtil.isNotEmpty(storeIds),"store_id", storeIds)
                .eq("status", ENABLED.getCode())
                .select("sum(expect_snap_total) as expect_snap_total"));
        return patrolSnapPlan == null ? 0L : patrolSnapPlan.getExpectSnapTotal();
    }

    @Override
    public TableDataInfo<PatrolSnapPlanVo> queryPageImagList(PatrolSnapQuery query, PageQuery pageQuery) {
        Page<PatrolSnapPlanVo> list = baseMapper.selectVoPageList(pageQuery.build(), query);
        if (CollUtil.isEmpty(list.getRecords())) {
            return TableDataInfo.build(list);
        }
        List<Long> storeIds = list.getRecords().stream().map(PatrolSnapPlanVo::getStoreId).distinct().toList();
        Map<Long, String> storeNameMap = remoteStoreService.selectNamesByStoreList(storeIds);
        list.getRecords().forEach(item -> {
            if (ObjUtil.isNotNull(item)
                    && ObjUtil.isNotNull(item.getStoreId())) {
                item.getNameMap().put(item.getStoreId(), storeNameMap.computeIfAbsent(item.getStoreId(), key -> null));
            }
        });
        return TableDataInfo.build(list);
    }

    /**
     * 根据ID更新最近图片地址和抓拍次数
     * @param id
     * @param imageUrl
     * @param snapTotal
     * @return
     */
    @Override
    public Boolean updateLatestImgUrlAndSnapTotalById(Long id, String imageUrl, Integer snapTotal) {
        Long snapTotalOld = baseMapper.selectOne(new LambdaQueryWrapper<PatrolSnapPlan>()
            .select(PatrolSnapPlan::getSnapTotal)
            .eq(PatrolSnapPlan::getPlanId, id)).getSnapTotal();
        if (baseMapper.update(new LambdaUpdateWrapper<PatrolSnapPlan>()
            .eq(PatrolSnapPlan::getPlanId, id)
            .set(PatrolSnapPlan::getLatestImgUrl, imageUrl)
            .set(PatrolSnapPlan::getSnapTotal, ObjUtil.isNull(snapTotalOld) ? snapTotal : snapTotalOld + snapTotal)) > 0) {
            return true;
        }
        throw new PatrolSnapException("更新失败");
    }

    @Override
    public Long queryExistByStoreIds(List<Long> storeIds) {
        return baseMapper.selectCount(new QueryWrapper<PatrolSnapPlan>()
            .select("DISTINCT store_id")
            .lambda()
            .in(ObjUtil.isNotNull(storeIds), PatrolSnapPlan::getStoreId, storeIds));
    }

    @Override
    public Set<Long> queryExistedDeviceIds() {
        List<PatrolSnapPlan> patrolSnapPlans = baseMapper.selectList();
        List<Long> deviceIds = patrolSnapPlans.stream().flatMap(item -> item.getDeviceIds().stream()).toList();
        return Set.copyOf(deviceIds);
    }

    /**
     * 删除抓拍计划
     *
     * @param storeId
     */
    @Override
    public void deletePatrolSnapPlan(Long storeId) {
        if(Objects.isNull(storeId)){
            throw new ServiceException("参数错误");
        }
        LambdaQueryWrapper<PatrolSnapPlan> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(PatrolSnapPlan::getStoreId,storeId);
        List<PatrolSnapPlan> patrolSnapPlans = baseMapper.selectList(wrapper);
        if (CollectionUtils.isNotEmpty(patrolSnapPlans)){
            baseMapper.deleteBatchIds(patrolSnapPlans);
        }
    }
}
